﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using Lucene.Net.Documents;
using Microsoft.TeamFoundation.VersionControl.Client;
using NLog;

namespace TfsGiggle.Core
{
    /// <summary>
    /// This is what is indexed
    /// </summary>
    internal class SourceControlDocument
    {
        private static Logger _logger = LogManager.GetCurrentClassLogger();
        private static ThreadLocal<Field> _fieldDocumentKey = new ThreadLocal<Field>(() => new Field(SourceControlDocument.FieldNames.DocumentKey, "", Field.Store.YES, Field.Index.NOT_ANALYZED));
        private static ThreadLocal<Field> _fieldModified = new ThreadLocal<Field>(() => new Field(SourceControlDocument.FieldNames.Modified, "", Field.Store.YES, Field.Index.NOT_ANALYZED));
        private static ThreadLocal<Field> _fieldContents = new ThreadLocal<Field>(() => new Field(SourceControlDocument.FieldNames.Contents, new StreamReader(Path.GetTempFileName())));

        /// <summary>
        /// Fieldnames for lucene documents
        /// </summary>
        internal static class FieldNames
        {
            /// <summary>
            /// Modified date
            /// </summary>
            public static readonly string Modified = "modified";

            /// <summary>
            /// Tokens for content
            /// </summary>
            public static readonly string Contents = "contents";

            /// <summary>
            /// Changeset id
            /// </summary>
            public static readonly string ChangesetId = "changeset_id";

            /// <summary>
            /// Comment
            /// </summary>
            public static readonly string ChangesetComment = "changeset_comment";

            /// <summary>
            /// Who made the change
            /// </summary>
            public static readonly string ChangesetCommitter = "changeset_committer";

            /// <summary>
            /// Workitem id
            /// </summary>
            public static readonly string WorkitemId = "workitem_id";

            /// <summary>
            /// Title
            /// </summary>
            public static readonly string WorkitemTitle = "workitem_title";

            /// <summary>
            /// A combined key of teamProjectCollectionId:path
            /// </summary>
            public static readonly string DocumentKey = "documentkey";
        }

        private SourceControlDocument() { }

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="item"></param>
        /// <param name="teamProjectCollectionId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        internal static Document Document(IItem item, string teamProjectCollectionId, string path)
        {
            if (item == null) throw new ArgumentNullException("item");
            if (string.IsNullOrWhiteSpace(teamProjectCollectionId)) throw new ArgumentNullException("teamProjectCollectionId");
            if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path");

            _logger.Trace("item:{0} teamProjectCollectionId:{1} path':{2}", item.ChangesetId, teamProjectCollectionId, path);

            var vcs = TfsSourceControl.Instance.GetVersionControlServer(teamProjectCollectionId);
            if (vcs == null)
            {
                _logger.Trace("Tfs services are currently unavailable");
                return null;
            }

            if (!vcs.ServerItemExists(path, ItemType.File))
            {
                _logger.Trace("path:{0} does not exist", path);
                return null;
            }

            // make a new, empty document
            Document doc = new Document();

            // Add the documentkey as a combination og teamprojectcollectionid and path
            // Use a field that is indexed (i.e. searchable), but don't tokenize the field into words.
            _fieldDocumentKey.Value.SetValue(IndexModel.GetDocumentKey(teamProjectCollectionId, path));
            doc.Add(_fieldDocumentKey.Value);

            // Add the last modified date of the file a field named "modified".  Use 
            // a field that is indexed (i.e. searchable), but don't tokenize the field
            // into words.
            // http://msdn.microsoft.com/en-us/library/az4se3k1.aspx#UniversalSortable
            _fieldModified.Value.SetValue(item.CheckinDate.ToUniversalTime().ToString("u"));
            doc.Add(_fieldModified.Value);

            // Specify a Reader,so that the text of the file is tokenized and indexed, but not stored.
            // Note that FileReader expects the file to be in the system's default encoding.
            // If that's not the case searching for special characters will fail.
            _fieldContents.Value.SetValue(new StreamReader(item.DownloadFile(), Encoding.GetEncoding(item.Encoding), true));
            doc.Add(_fieldContents.Value);

            var workItems = new Dictionary<int, string>();
            int numberOfChangesets = 0;
            foreach (IChangeset changeset in item.VersionControlServer.QueryHistory(path, VersionSpec.Latest, 0, RecursionType.None, "", null, VersionSpec.Latest, Int32.MaxValue, true, false))
            {
                var fieldChangesetId = new Field(SourceControlDocument.FieldNames.ChangesetId, changeset.ChangesetId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED);
                var fieldChangesetComment = new Field(SourceControlDocument.FieldNames.ChangesetComment, changeset.Comment, Field.Store.YES, Field.Index.ANALYZED);
                var fieldChangesetCommitter = new Field(SourceControlDocument.FieldNames.ChangesetCommitter, changeset.Committer, Field.Store.YES, Field.Index.NOT_ANALYZED);
                doc.Add(fieldChangesetId);
                doc.Add(fieldChangesetComment);
                doc.Add(fieldChangesetCommitter);

                TfsWorkItem.AddWorkItems(changeset, workItems);
                numberOfChangesets++;
                _logger.Trace("changeset({0}) added", changeset.ChangesetId);
            }
            _logger.Trace("{0} distinct changesets found for {1}", numberOfChangesets, path);

            foreach (var workItem in workItems)
            {
                var fieldWorkitemId = new Field(SourceControlDocument.FieldNames.WorkitemId, workItem.Key.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED);
                var fieldWorkitemTitle = new Field(SourceControlDocument.FieldNames.WorkitemTitle, workItem.Value, Field.Store.YES, Field.Index.ANALYZED);
                doc.Add(fieldWorkitemId);
                doc.Add(fieldWorkitemTitle);
                _logger.Trace("workitem({0}) added", workItem.Key);
            }
            _logger.Trace("exit. {0} distinct workitems found for {1}", workItems.Count, path);
            return doc;
        }
    }
}